package com.iakuil.app.common.restful.aspect;

import com.iakuil.app.common.core.annotation.Log;
import com.iakuil.app.common.core.util.*;
import com.iakuil.app.domain.dto.event.OperationLogEvent;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Map;

/**
 * 操作日志记录截面
 *
 * @author Kai
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 需要过滤的请求参数
     */
    public static final String[] EXCLUDE_REQUEST_PARAM = {"pwd", "password", "oldPassword", "newPassword", "confirmPassword"};

    /**
     * 需要过滤的JSON字段
     */
    private static final String EXCLUDE_PROP_TEMPLATE1 = "\"(%s)\":.*?,";

    /**
     * 需要过滤的JSON字段（结尾处）
     */
    private static final String EXCLUDE_PROP_TEMPLATE2 = ",*\"(%s)\":.*}";

    /**
     * 响应成功后执行
     *
     * @param joinPoint 切点
     * @param logAnno   操作日志注解对象
     * @param resBody   Response Body
     */
    @AfterReturning(pointcut = "@annotation(logAnno)", returning = "resBody")
    public void doAfterReturning(JoinPoint joinPoint, Log logAnno, Object resBody) {
        publishLogEvent(joinPoint, logAnno, null, resBody);
    }

    /**
     * 响应异常后执行
     *
     * @param joinPoint 切点
     * @param ex        异常
     */
    @AfterThrowing(value = "@annotation(logAnno)", throwing = "ex")
    public void doAfterThrowing(JoinPoint joinPoint, Log logAnno, Exception ex) {
        publishLogEvent(joinPoint, logAnno, ex, null);
    }

    /**
     * 组装并发布日志事件
     */
    protected void publishLogEvent(final JoinPoint joinPoint, Log logAnno, final Exception ex, Object resBody) {
        OperationLogEvent event = new OperationLogEvent();
        if (ex == null) {
            event.setStatus(OpStatus.SUCCESS.ordinal());
        } else {
            event.setStatus(OpStatus.FAIL.ordinal());
            event.setErrorMsg(StringUtils.substring(ex.getMessage(), 0, 2000));
        }
        event.setOperIp(ServletUtils.getClientIP());
        event.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
        // 此处暂时例旧，后续再优化
        event.setOperName(SecurityUtils.getCurrentUserId().toString());
        event.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
        event.setRequestMethod(ServletUtils.getRequest().getMethod());

        // 根据注解配置，处理负载，包括请求参数、请求体和响应体
        processPayload(joinPoint.getArgs(), logAnno, event, resBody);
        // 发布操作日志事件（微服务场景可以使用MQ）
        SpringUtils.context().publishEvent(event);
    }

    /**
     * 处理负载
     *
     * @param reqParams 参数列表
     * @param logAnno   日志注解
     * @param event     操作日志事件
     */
    private void processPayload(Object[] reqParams, Log logAnno, OperationLogEvent event, Object resBody) {
        event.setBusinessType(logAnno.type().ordinal());
        event.setTitle(logAnno.title());

        // Handling Request Parameters
        if (logAnno.isSaveRequest()) {
            processRequestParams(event, reqParams, logAnno.excludeParams());
        }
        // Handling Response Body
        if (logAnno.isSaveResponse() && ObjectUtils.isNotNull(resBody)) {
            event.setJsonResult(StringUtils.substring(JsonUtils.bean2Json(resBody), 0, 2000));
        }
    }

    /**
     * 处理Request Parameter
     */
    private void processRequestParams(OperationLogEvent event, Object[] args, String[] excludes) {
        Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        String requestMethod = event.getRequestMethod();
        if (MapUtils.isEmpty(paramsMap) && HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            String params = joinAfterClear(args, excludes);
            event.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
            MapUtils.removeAny(paramsMap, EXCLUDE_REQUEST_PARAM);
            MapUtils.removeAny(paramsMap, excludes);
            event.setOperParam(StringUtils.substring(JsonUtils.bean2Json(paramsMap), 0, 2000));
        }
    }

    /**
     * 参数过滤拼装
     */
    private String joinAfterClear(Object[] args, String[] excludes) {
        if (args == null) {
            return null;
        }

        StringBuilder params = new StringBuilder();
        for (Object arg : args) {
            if (ObjectUtils.isNotNull(arg) && needFilter(arg)) {
                String json = JsonUtils.bean2Json(arg);
                String joinedProps = StringUtils.join(excludes, "|");
                json = json.replaceAll(String.format(EXCLUDE_PROP_TEMPLATE1, joinedProps), "");
                json = json.replaceAll(String.format(EXCLUDE_PROP_TEMPLATE2, joinedProps), "}");
                params.append(json).append(" ");
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象
     */
    private boolean needFilter(final Object obj) {
        return !noNeedFilter(obj);
    }

    @SuppressWarnings("rawtypes")
    private boolean noNeedFilter(final Object obj) {
        Class<?> clazz = obj.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) obj;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) obj;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse || obj instanceof BindingResult;
    }

    /**
     * 操作状态
     */
    private enum OpStatus {
        /**
         * 成功
         */
        SUCCESS,

        /**
         * 失败
         */
        FAIL,
    }
}
